// Created by songgf on 25-4-17.

#include "inp_to_bdf_convert.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>

int InpToBdfConvertor::convert(const std::string &inpPath,
                               const std::string &bdfPath) {
  std::cout << inpPath << std::endl;
  if (parseInpFile(inpPath) != 0) {
    std::cerr << "Failed to parse inp file" << std::endl;
    return -1;
  }

  if (generateBdfFile(bdfPath) != 0) {
    std::cerr << "Failed to write BDF file: " << bdfPath << std::endl;
    return -1;
  }

  return 0;
}

int InpToBdfConvertor::generateBdfFile(const std::string &path) {
  std::ofstream out(path);
  if (!out.is_open()) {
    std::cerr << "Failed to open output BDF file: " << path << std::endl;
    return -1;
  }

  out << std::fixed << std::setprecision(3);

  // --- 写入 GRID 节点 ---
  for (const auto &node : nodes) {
    out << std::left << std::setw(8) << "GRID" << std::right << std::setw(6)
        << node.id << std::fixed << std::setprecision(3) << std::setw(16)
        << node.x << std::setw(8) << node.y << std::setw(8) << node.z << "\r\n";
  }

  for (const auto &elem : elements) {
    if (elem.type == "C3D20") {
      writeCHEXA(out, elem);
    }
  }

  for (const auto &mat : materials) {
    writeMAT1(out, mat.second); // 假设 materials 是 map<int, Material>
  }

  out.close();
  return 0;
}

std::string InpToBdfConvertor::mapToBdfElementType(const std::string &inpType) {
  if (inpType == "CPS4" || inpType == "S4" || inpType == "S4R" ||
      inpType == "CPE4" || inpType == "CQUAD")
    return "CQUAD4";
  if (inpType == "CPS3" || inpType == "S3" || inpType == "S3R" ||
      inpType == "CPE3" || inpType == "CTRIA")
    return "CTRIA3";
  return "UNKNOWN";
}

void InpToBdfConvertor::trim(std::string &s) {
  if (s.empty())
    return;

  s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
            return !std::isspace(ch);
          }));
  s.erase(std::find_if(s.rbegin(), s.rend(),
                       [](unsigned char ch) { return !std::isspace(ch); })
              .base(),
          s.end());
}

int InpToBdfConvertor::parseInpFile(const std::string &path) {
  std::fstream in(path);
  if (!in.is_open()) {
    std::cerr << "Failed to open INP file: " << path << std::endl;
    return -1;
  }

  std::string line;
  std::string currentElemType;
  std::string currentElsetId;
  std::string currentMaterialId;
  std::string currentShellSectionElsetId;
  std::string currentShellMaterialId;
  bool readingNodes = false, readingElements = false;
  bool readingElset = false, readingNset = false;
  bool readingShellSection = false, readingMaterial = false,
       readingElastic = false;

  while (getline(in, line)) {
    trim(line);
    if (line.empty())
      continue;

    if (line[0] == '*') {
      readingNodes = readingElements = readingElset = readingNset = false;
      readingShellSection = readingMaterial = readingElastic = false;

      if (line.find("*Node") != std::string::npos) {
        readingNodes = true;
      } else if (line.find("*Element") != std::string::npos) {
        currentElemType = parseElemType(line);
        auto params = getParamMap(line);
        currentElsetId = params["Elset"];
        if (!currentElsetId.empty()) {
          elsets.push_back({currentElsetId});
        }

        parseElementBlock(in, currentElemType);
        continue;
      } else if (line.find("*ELSET") != std::string::npos) {
        currentElsetId = getParamMap(line)["ELSET"];
        if (!currentElsetId.empty()) {
          elsets.push_back({currentElsetId});
        }
        readingElset = true;
      } else if (line.find("*NSET") != std::string::npos) {
        currentElsetId = getParamMap(line)["NSET"];
        if (!currentElsetId.empty()) {
          nodeSets.push_back({currentElsetId});
        }
        readingNset = true;
      } else if (line.find("*SHELL SECTION") != std::string::npos ||
                 line.find("*SOLID SECTION") != std::string::npos) {
        auto params = getParamMap(line);
        currentShellSectionElsetId = params["ELSET"];
        currentShellMaterialId = params["MATERIAL"];
        readingShellSection = true;
      } else if (line.find("*MATERIAL") != std::string::npos) {
        currentMaterialId = getParamMap(line)["NAME"];
        readingMaterial = true;
      } else if (line.find("*Elastic") != std::string::npos) {
        readingElastic = true;
      } else if (line.find("*STEP") != std::string::npos) {
        break;
      }
      continue;
    }

    if (readingNodes) {
      parseNode(line);
    } else if (readingElements) {
      parseElement(line, currentElemType);
      if (!currentElsetId.empty()) {
        std::stringstream ss(line);
        std::string token;
        if (std::getline(ss, token, ',')) {
          int eid = std::stoi(token);
          for (auto &elset : elsets) {
            if (elset.id == currentElsetId) {
              elset.elemIds.push_back(eid);
              break;
            }
          }
        }
      }
    } else if (readingElset) {
      auto &elset = elsets.back();
      std::stringstream ss(line);
      std::string token;
      while (std::getline(ss, token, ',')) {
        trim(token);
        elset.elemIds.push_back(std::stoi(token));
      }
    } else if (readingNset) {
      auto &nset = nodeSets.back();
      std::stringstream ss(line);
      std::string token;
      while (std::getline(ss, token, ',')) {
        trim(token);
        nset.nodeIds.push_back(std::stoi(token));
      }
    } else if (readingShellSection) {
      parseShellSection(line, currentShellSectionElsetId,
                        currentShellMaterialId);
    } else if (readingElastic) {
      parseElastic(line, currentMaterialId);
    }
  }

  in.close();
  return 0;
}

void InpToBdfConvertor::parseElastic(const std::string &line,
                                     const std::string &materialId) {
  if (materialId.empty() || line.empty())
    return;

  std::stringstream ss(line);
  std::string eStr, nuStr;
  std::getline(ss, eStr, ',');
  std::getline(ss, nuStr, ',');

  double E = std::stod(eStr);
  double nu = std::stod(nuStr);

  Material mat;
  mat.id = materialId;
  mat.E = E;
  mat.nu = nu;

  materials[materialId] = mat;
}

void InpToBdfConvertor::parseElement(const std::string &line,
                                     const std::string &elemType) {
  std::stringstream ss(line);
  std::string token;
  std::vector<int> values;

  while (std::getline(ss, token, ',')) {
    trim(token);
    if (!token.empty()) {
      values.push_back(std::stoi(token));
    }
  }

  if (values.size() >= 2) {
    Element elem;
    elem.id = values[0];
    elem.type = elemType;
    elem.nodeIds.assign(values.begin() + 1, values.end());
    elements.push_back(elem);
  } else {
    std::cerr << "Invalid element line: " << line << std::endl;
  }
}

void InpToBdfConvertor::parseNode(const std::string &line) {
  std::stringstream ss(line);
  std::string idStr, xStr, yStr, zStr;
  std::getline(ss, idStr, ',');
  std::getline(ss, xStr, ',');
  std::getline(ss, yStr, ',');
  std::getline(ss, zStr, ',');

  trim(idStr);
  trim(xStr);
  trim(yStr);
  trim(zStr);
  Node node;
  node.id = std::stoi(idStr);
  node.x = std::stod(xStr);
  node.y = std::stod(yStr);
  node.z = std::stod(zStr);

  nodes.push_back(node);
}

std::string InpToBdfConvertor::parseElemType(const std::string &line) {
  std::map<std::string, std::string> paramMap = getParamMap(line);
  auto it = paramMap.find("TYPE");
  if (it != paramMap.end()) {
    return it->second;
  }
  return "";
}

std::map<std::string, std::string>
InpToBdfConvertor::getParamMap(const std::string &line) {
  std::map<std::string, std::string> paramMap;
  std::regex pairRegex(R"((\w+)\s*=\s*([^,]+))");
  auto begin = std::sregex_iterator(line.begin(), line.end(), pairRegex);
  auto end = std::sregex_iterator();
  for (auto it = begin; it != end; ++it) {
    paramMap[toUpperCopy(it->str(1))] = it->str(2);
  }
  return paramMap;
}

void InpToBdfConvertor::parseShellSection(const std::string &line,
                                          const std::string &elsetId,
                                          const std::string &materialId) {
  if (elsetId.empty())
    return;

  for (auto &elset : elsets) {
    if (elset.id == elsetId) {
      elset.materialId = materialId;
      elset.thickness = std::stod(line);
    }
  }
}

void InpToBdfConvertor::parseElset(
    const std::string &line, std::map<std::string, std::string> parameters) {
  if (parameters.find("elset") == parameters.end())
    return;
  std::string elsetId = parameters["elset"];

  Elset elset;
  elset.id = elsetId;

  if (parameters.find("generate") != parameters.end() ||
      line.find(',') != std::string::npos) {
    // 支持形如 1, 10, 1
    std::stringstream ss(line);
    std::string startStr, endStr, incStr;
    std::getline(ss, startStr, ',');
    std::getline(ss, endStr, ',');
    std::getline(ss, incStr, ',');

    int start = std::stoi(startStr);
    int end = std::stoi(endStr);
    int step = incStr.empty() ? 1 : std::stoi(incStr);

    for (int i = start; i <= end; i += step) {
      elset.elemIds.push_back(i);
    }
  } else {
    std::stringstream ss(line);
    std::string token;
    while (std::getline(ss, token, ',')) {
      trim(token);
      if (!token.empty()) {
        elset.elemIds.push_back(std::stoi(token));
      }
    }
  }

  elsets.push_back(elset);
}

void InpToBdfConvertor::writeCHEXA(std::ofstream &out,
                                   const InpToBdfConvertor::Element &elem) {
  const std::vector<int> &nodes = elem.nodeIds;
  const size_t nodeCount = nodes.size();

  // 第1行：CHEXA + elem.id + propertyId + 最多5个节点
  out << std::left << std::setw(8) << "CHEXA" << std::setw(8) << elem.id
      << std::setw(8) << 1;

  size_t i = 0;
  for (; i < 6 && i < nodeCount; ++i) {
    out << std::setw(8) << nodes[i];
  }
  out << "\r\n";

  // 后续行：每行最多8个节点编号，前缀8空格
  while (i < nodeCount) {
    out << std::setw(8) << " ";
    for (int j = 0; j < 8 && i < nodeCount; ++j, ++i) {
      out << std::setw(8) << nodes[i];
    }
    out << "\r\n";
  }
}

std::string InpToBdfConvertor::toUpperCopy(const std::string &input) {
  std::string result = input;
  std::transform(result.begin(), result.end(), result.begin(),
                 [](unsigned char c) { return std::toupper(c); });
  return result;
}

void InpToBdfConvertor::writeMAT1(std::ofstream &out, const Material &mat) {
  out << "MAT1*  " << std::setw(8) << mat.id << std::setw(16) << mat.E
      << std::setw(16) << mat.G << std::setw(16) << mat.nu << "\n"
      << "*      " << std::setw(16) << mat.rho << std::setw(16) << mat.alpha
      << "\n";
}

void InpToBdfConvertor::parseElementBlock(std::istream &in,
                                          const std::string &type) {
  std::string line;
  std::vector<std::string> tokens;

  while (std::getline(in, line)) {
    trim(line);
    if (line.empty() || line[0] == '*') {
      break;
    }

    std::stringstream ss(line);
    std::string token;
    while (std::getline(ss, token, ',')) {
      trim(token);
      if (!token.empty()) {
        tokens.push_back(token);
      }
    }

    while (tokens.size() >= 21) {
      Element elem;
      elem.type = type;
      elem.id = std::stoi(tokens[0]);
      for (int i = 1; i <= 20; ++i) {
        elem.nodeIds.push_back(std::stoi(tokens[i]));
      }
      elements.push_back(elem);
      tokens.erase(tokens.begin(), tokens.begin() + 21);
    }
  }
}
